08. Mind The Line
In this quiz you'll use MPC to follow the trajectory along a line.
Steps:
- Set N and dt .
- Fit the polynomial to the waypoints.
- Calculate initial cross track error and orientation error values.
- Define the components of the cost function (state, actuators, etc). You may use the methods previously discussed or make up something, up to you!
- Define the model constraints. These are the state update equations defined in the Vehicle Models module.
Before you begin let's go over the libraries you'll use for this quiz and the following project.
Ipopt
Ipopt is the tool we'll be using to optimize the control inputs [\delta_1, a_1, …, \delta_{N-1}, a_{N-1}] . It's able to find locally optimal values (non-linear problem!) while keeping the constraints set directly to the actuators and the constraints defined by the vehicle model. Ipopt requires we give it the jacobians and hessians directly - it does not compute them for us. Hence, we need to either manually compute them or have a library do this for us. Luckily, there is a library called CppAD which does exactly this.
CppAD
CppAD is a library we'll use for automatic differentiation. By using CppAD we don't have to manually compute derivatives, which is tedious and prone to error.
In order to use CppAD effectively, we have to use its types instead of regular
double
or
std::vector
types.
Additionally math functions must be called from CppAD. Here's an example of calling
pow
:
CppAD::pow(x, 2);
// instead of
pow(x, 2);
Luckily most elementary math operations are overloaded. So calling
*
,
+
,
-
,
/
will work as intended as long as it's called on
CppAD<double>
instead of
double
. Most of this is done for you and there are examples to draw from in the code we provide.
Code Structure
We've filled in most of the quiz starter code for you. The goal of this quiz is really just about getting everything to work as intended.
That said, it may be tricky to decipher some elements of the starter code, so we will walk you through it.
There are two main components in
MPC.cpp
:
-
vector<double> MPC::Solve(Eigen::VectorXd x0, Eigen::VectorXd coeffs)
method -
FG_eval
class
There are also a few small
TODO
s in
main.cpp
which are mostly self-explanatory, so we won't cover them here.
MPC::Solve
x0
is the initial state
[x ,y , \psi, v, cte, e\psi]
,
coeffs
are the coefficients of the fitting polynomial. The bulk of this method is setting up the vehicle model constraints (
constraints
) and variables (
vars
) for Ipopt.
Variables
double x = x0[0];
double y = x0[1];
double psi = x0[2];
double v = x0[3];
double cte = x0[4];
double epsi = x0[5];
...
// Set the initial variable values
vars[x_start] = x;
vars[y_start] = y;
vars[psi_start] = psi;
vars[v_start] = v;
vars[cte_start] = cte;
vars[epsi_start] = epsi;
Note Ipopt expects all the constraints and variables as vectors. For example, suppose
N
is 5, then the structure of
vars
a 38-element vector:
vars[0],…,vars[4] -> [x_1, …., x_5]
vars[5],…,vars[9] -> [y_1, …., y_5]
vars[10],…,vars[14] -> [\psi_1, …., \psi_5]
vars[15],…,vars[19] -> [v_1, …., v_5]
vars[20],…,vars[24] -> [cte_1, …., cte_5]
vars[25],…,vars[29] -> [e\psi_1, …., e\psi_5]
vars[30],…,vars[33] -> [\delta_1, …., \delta_4]
vars[34],…,vars[37] -> [a_1, …., a_4]
We then set lower and upper bounds on the variables. Here we set the range of values \delta to [-25, 25] in radians:
for (int i = delta_start; i < a_start; ++i) {
vars_lowerbound[i] = -0.436332;
vars_upperbound[i] = 0.436332;
}
Constraints
Next the we set the lower and upper bounds on the constraints.
Consider, for example:
This expresses that x_{t+1} MUST be equal to x_t + v_t * cos(\psi_t) * dt . Put differently:
The equation above simplifies the upper and lower bounds of the constraint: both must be 0.
This can be generalized to the other equations as well:
for (int i = 0; i < n_constraints; ++i) {
constraints_lowerbound[i] = 0;
constraints_upperbound[i] = 0;
}
FG_eval
The
FG_eval
class has the constructor:
FG_eval(Eigen::VectorXd coeffs) { this->coeffs = coeffs; }
where
coeffs
are the coefficients of the fitted polynomial.
coeffs
will be used by the cross track error and heading error equations.
The
FG_eval
class has only one method:
void operator()(ADvector& fg, const ADvector& vars)
vars
is the vector of variables (from the previous section) and
fg
is the vector of constraints.
One complication:
fg[0]
stores the cost value, so the
fg
vector is 1 element larger than it was in
MPC::Solve
.
Here in
operator()
you'll define the cost function and constraints.
x
is already completed:
for (int t = 1; t < N; ++t) {
AD<double> x1 = vars[x_start + t];
AD<double> x0 = vars[x_start + t - 1];
AD<double> psi0 = vars[psi_start + t - 1];
AD<double> v0 = vars[v_start + t - 1];
// Here's `x` to get you started.
// The idea here is to constraint this value to be 0.
//
// NOTE: The use of `AD<double>` and use of `CppAD`!
// CppAD can compute derivatives and pass these to the solver.
/**
* TODO: Setup the rest of the model constraints
*/
fg[1 + x_start + t] = x1 - (x0 + v0 * CppAD::cos(psi0) * dt);
}
Note that we start the loop at
t=1
, because the values at
t=0
are set to our initial state - those values are not calculated by the solver.
An
FG_eval
object is created in
MPC::Solve
:
FG_eval fg_eval(coeffs);
This is then used by Ipopt to find the lowest cost trajectory:
// place to return solution
CppAD::ipopt::solve_result<Dvector> solution;
// solve the problem
CppAD::ipopt::solve<Dvector, FG_eval>(
options, vars, vars_lowerbound, vars_upperbound, constraints_lowerbound,
constraints_upperbound, fg_eval, solution);
The filled in
vars
vector is stored as
solution.x
and the cost as
solution.obj_value
.
Complete the Model Predictive Control quiz from here .